// Copyright (C) ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
//
// $Id: document.cpp 209 2008-08-28 10:05:39Z heavyzheng $
//

#ifndef ZZHENG_XML_SOURCE
#define ZZHENG_XML_SOURCE
#endif // ZZHENG_XML_SOURCE

#include <zzheng/xml/dom/libxml2/document.hpp>
#include <zzheng/xml/dom/libxml2/utility.hpp>
#include <zzheng/xml/dom/libxml2/node.hpp>
#include <zzheng/xml/dom/libxml2/element.hpp>
#include <zzheng/xml/dom/libxml2/attribute.hpp>
#include <zzheng/xml/dom/libxml2/text.hpp>
#include <zzheng/xml/dom/libxml2/comment.hpp>

#include <zzheng/xml/dom/exception.hpp>

#include <libxml/tree.h>

#ifdef ZZHENG_MSVC
#  pragma warning(push)
#  pragma warning(disable: 4127 4511 4512 4701 4819)
#endif // ZZHENG_MSVC

#include <boost/numeric/conversion/cast.hpp>
#include <boost/lexical_cast.hpp>

#ifdef ZZHENG_MSVC
#  pragma warning(pop)
#endif // ZZHENG_MSVC

#include <boost/algorithm/string/predicate.hpp>

#include <cassert>
#include <string>
#include <vector>
#include <algorithm>

namespace zzheng {
namespace xml {
namespace dom {
namespace libxml2_ {

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // anonymous namespace
    //

    namespace {

        //! The prefix of the namespace prefix generated by the document.
        static const std::string PRE_PREFIX = "ns_";

        int count_prefix(const std::string& prefix, int counter) {
            int suffix_number = 0;
            if (prefix.size() > PRE_PREFIX.size() && boost::starts_with(prefix, PRE_PREFIX)) {
                // Convert the suffix to a number.
                for (std::string::size_type i = PRE_PREFIX.size(); i < prefix.size(); ++i) {
                    if (prefix.at(i) >= '0' && prefix.at(i) <= '9') {
                        suffix_number = 10 * suffix_number + (prefix.at(i) - '0');
                    } else {
                        suffix_number = 0; // reset to 0 if the suffix is not a valid number.
                        break;
                    }
                }
            }
            // Return the maximal value between counter and suffix.
            return (std::max)(suffix_number, counter);
       }

        int count_prefix_for_namespace(xmlNs* ns, int counter) {
            for (xmlNs* i = ns; i != 0; i = i->next) {
                if (i->prefix != 0) {
                    counter = count_prefix(to_chars(i->prefix), counter);
                }
            }
            return counter;
        }

        int count_prefix_for_element(xmlNode* elem, int counter) {
            if (elem != 0 && elem->type == XML_ELEMENT_NODE) {
                counter = count_prefix_for_namespace(elem->nsDef, counter);
                for (xmlNode* i = elem->children; i != 0; i = i->next) {
                    counter = count_prefix_for_element(i, counter);
                }
            }
            return counter;
        }

        int count_prefix_for_document(xmlDoc* doc) {
            int counter = 0;
            if (doc != 0) {
                counter = count_prefix_for_namespace(doc->oldNs, counter);
                counter = count_prefix_for_element(xmlDocGetRootElement(doc), counter);
            }
            return counter;
        }

    } // anonymous namespace


    ////////////////////////////////////////////////////////////////////////////////////////////////
    // constructor/destructor
    //

    document::document(): raw_(0), counter_(0), orphans_() {
        raw_ = xmlNewDoc(to_xml_chars("1.0"));
        if (raw_ == 0) {
            throw internal_dom_error("Fail to create libxml2 document: xmlNewDoc() returns null.");
        }
        raw_->_private = this;
    }

    document::document(xmlDoc* px): raw_(px), counter_(0), orphans_() {
        assert(raw_ != 0 && raw_->type == XML_DOCUMENT_NODE);
        raw_->_private = this;
        // TODO: counter_ = count_prefix_for_document(raw_);
    }

    document::~document() {
        // TODO: Free all orphan nodes.
        typedef std::vector<node*>::iterator iterator;
        for (iterator i = orphans_.begin(); i != orphans_.end(); ++i) {
            if (is_my_orphan_(*i)) {
                node* orphan = *i;
                xmlUnlinkNode(orphan->raw());
                xmlFreeNode(orphan->raw());
            }
        }
        // Free the underlying libxml2 document.
        raw_->_private = 0;
        xmlFreeDoc(raw_);
        raw_ = 0;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // property access
    //

    std::string document::encoding() const {
        if (raw()->encoding != 0) {
            return to_chars(raw()->encoding);
        } else {
            return std::string();
        }
    }

    std::string document::version() const {
        return "1.0"; // TODO: always 1.0?
    }

    bool document::empty() const {
        return ( root_() == 0 );
    }

    basic_node_ptr<element> document::root() {
        return basic_node_ptr<element>( const_cast<element*>(root_()) );
    }

    basic_node_ptr<const element> document::root() const {
        return basic_node_ptr<const element>( root_() );
    }

    std::string document::str() const {
        std::string tmp;
        str(tmp);
        return tmp;
    }

    void document::str(std::string& str) const {
        // Dump the document to a memory buffer: it is up to the caller to free the buffer.
        xmlChar* buffer = 0;
        int size = 0;
        xmlDocDumpMemory( const_cast<xmlDoc*>(raw()), &buffer, &size );
        // Save the buffer's contents to string.
        if (size > 0 && buffer != 0) {
            str = to_chars(buffer);
        } else {
            str = std::string();
        }
        // Free the buffer.
        xmlFree(buffer);
        buffer = 0;
    }

    void document::save_to_file(const std::string& file_name) const {
        int size = xmlSaveFormatFileEnc( file_name.c_str(),
                                         const_cast<xmlDoc*>(raw()),
                                         0,
                                         0 );
        (size > 0);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // modifiers
    //

    void document::clear() {
        // TODO: Free all orphan nodes.
        typedef std::vector<node*>::iterator iterator;
        for (iterator i = orphans_.begin(); i != orphans_.end(); ++i) {
            if (is_my_orphan_(*i)) {
                node* orphan = *i;
                xmlUnlinkNode(orphan->raw());
                xmlFreeNode(orphan->raw());
            }
        }
        // Remove and free the root element from the document.
        xmlNode* root = xmlDocGetRootElement(raw());
        if (root != 0) {
            xmlUnlinkNode(root);
            xmlFreeNode(root);
        }
        // TODO: Reset the namespace prefix counter.
        // counter_ = 0;
    }

    basic_node_ptr<element> document::reset_root(const element_proxy& elem) {

        // Create a new element without namespace.
        xmlNode* px = xmlNewDocNode( raw(), 0, to_xml_chars(elem.name().c_str()), 0 );
        if (px == 0) {
            std::string errmsg = "Fail to create libxml2 element: xmlNewDocNode() returns null.";
            throw internal_dom_error(errmsg.c_str());
        } else if (px->_private == 0) {
            std::string errmsg = "Fail to create libxml2 element: element contains null private.";
            throw internal_dom_error(errmsg.c_str());
        }

        // Reset the root element, and set the namespace URI.
        basic_node_ptr<element> root = reset_root_(px);
        root->set_uri(elem.uri());
        return root;
    }

    basic_node_ptr<element> document::reset_root(const element& elem) {
        return reset_root_(elem.clone_raw(true));
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // node creation
    //

    basic_node_ptr<element> document::make_element(const std::string& name) const {
        // Create a new libxml2 element node.
        xmlNode* px = xmlNewDocNode( const_cast<xmlDoc*>(raw()),
                                     0,
                                     to_xml_chars(name.c_str()),
                                     0 );
        if (px == 0) {
            throw dom_error("Fail to create new element: xmlNewDocNode() returns null.");
        }
        // Push the new libxml2 element to the orphan list and return.
        element* elem = static_cast<element*>(px->_private);
        add_orphan_(elem);
        return basic_node_ptr<element>(elem);
    }

    basic_node_ptr<attribute> document::make_attribute(const std::string& name,
                                                       const std::string& value) const {
        // Create a new libxml2 attribute node.
        xmlAttr* px = xmlNewDocProp( const_cast<xmlDoc*>(raw()),
                                     to_xml_chars(name.c_str()),
                                     to_xml_chars(value.c_str()) );
        if (px == 0) {
            throw dom_error("Fail to create attribute: xmlNewDocProp() returns null.");
        }
        // Push the new libxml2 attribute to the orphan list and return.
        attribute* attr = static_cast<attribute*>(px->_private);
        add_orphan_(attr);
        return basic_node_ptr<attribute>(attr);
    }

    basic_node_ptr<text> document::make_text(const std::string& value) const {
        // Create a new libxml2 text node.
        xmlNode* px = xmlNewDocText( const_cast<xmlDoc*>(raw()),
                                     to_xml_chars(value.c_str()) );
        if (px == 0) {
            throw dom_error("Fail to create text node: xmlNewDocText() returns null.");
        }
        // Push the new libxml2 text node to the orphan list and return.
        text* txt = static_cast<text*>(px->_private);
        add_orphan_(txt);
        return basic_node_ptr<text>(txt);
    }

    basic_node_ptr<text> document::make_cdata(const std::string& value) const {
        // Create a new libxml2 CDATA section node.
        xmlNode* px = xmlNewCDataBlock( const_cast<xmlDoc*>(raw()),
                                        to_xml_chars(value.c_str()),
                                        boost::numeric_cast<int>(value.size()) );
        if (px == 0) {
            throw dom_error("Fail to create CDATA node: xmlNewCDataBlock() returns null.");
        }
        // Push the new libxml2 CDATA section node to the orphan list and return.
        text* cdata = static_cast<text*>(px->_private);
        add_orphan_(cdata);
        return basic_node_ptr<text>(cdata);
    }

    basic_node_ptr<comment> document::make_comment(const std::string& value) const {
        // Create a new libxml2 comment node.
        xmlNode* px = xmlNewDocComment( const_cast<xmlDoc*>(raw()),
                                        to_xml_chars(value.c_str()) );
        if (px == 0) {
            throw dom_error("Fail to create comment node: xmlNewDocComment() returns null.");
        }
        // Push the new libxml2 comment node to the orphan list and return.
        comment* cmnt = static_cast<comment*>(px->_private);
        add_orphan_(cmnt);
        return basic_node_ptr<comment>(cmnt);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // namespace prefix generation
    //

    std::string document::next_namespace_prefix() const {
        // TODO: how to guarantee that prefix is unique within the document?
        // When the root element is reset, or when an XML subtree is adopted into this document
        // (which is an operation on an element), the prefix counter must be recalculated.
        return ( PRE_PREFIX + boost::lexical_cast<std::string>(++counter_) );
    }


    ////////////////////////////////////////////////////////////////////////////////////////////////
    // private
    //

    const element* document::root_() const {
        const xmlNode* px = xmlDocGetRootElement( const_cast<xmlDoc*>(raw()) );
        if (px != 0) {
            return static_cast<const element*>(px->_private);
        } else {
            return 0;
        }
    }

    basic_node_ptr<element> document::reset_root_(xmlNode* px) {
        assert(px != 0);
        xmlNode* px_old = xmlDocSetRootElement(raw(), px);
        if (px_old != 0) {
            xmlUnlinkNode(px_old);
            xmlFreeNode(px_old);
        }
        // TODO: Now the document contains only a root element without namespace: it is safe to
        // reset the namespace prefix counter.
        // counter_ = 0;
        return basic_node_ptr<element>( static_cast<element*>(px->_private) );
    }

    bool document::is_my_orphan_(const node* orphan) const {
        return (  orphan != 0                 // the node is not null, and...
               && orphan->is_orphan()         // is an orphan node, and...
               && &(orphan->doc()) == this ); // belongs to this document.
    }

    void document::add_orphan_(node* orphan) const {
        assert(is_my_orphan_(orphan));
        if (std::find(orphans_.begin(), orphans_.end(), orphan) == orphans_.end()) {
            orphans_.push_back(orphan);
        }
    }

    void document::remove_orphan_(node*) const {
        // TODO: implement me!
    }

} // namespace zzheng::xml::dom::libxml2_
} // namespace zzheng::xml::dom
} // namespace zzheng::xml
} // namespace zzheng




